// base64.hpp 
// Autor Konstantin Pilipchuk
// mailto:lostd@ukr.net
//
//

/**

\code
// Base64
bool binaryFromXml(std::string const& valueXml, int* offset)
{
    size_t valueEnd = valueXml.find('<', *offset);
    if (valueEnd == std::string::npos)
        return false;     // No end tag;

    _type = TypeBase64;
    std::string asString = valueXml.substr(*offset, valueEnd-*offset);
    _value.asBinary = new BinaryData();
    // check whether base64 encodings can contain chars xml encodes...

    // convert from base64 to binary
    int iostatus = 0;
    base64<char> decoder;
    std::back_insert_iterator<BinaryData> ins = std::back_inserter(*(_value.asBinary));
    decoder.get(asString.begin(), asString.end(), ins, iostatus);

    *offset += int(asString.length());
    return true;
}

std::string binaryToXml() const
{
    // convert to base64
    std::vector<char> base64data;
    int iostatus = 0;
    base64<char> encoder;
    std::back_insert_iterator<std::vector<char> > ins = std::back_inserter(base64data);
    encoder.put(_value.asBinary->begin(), _value.asBinary->end(), ins, iostatus, base64<>::crlf());

    // Wrap with xml
    std::string xml = "<value><base64>";
    xml.append(base64data.begin(), base64data.end());
    xml += "</base64></value>";
    return xml;
}
\endcode
*/

#ifndef GOOGLE_PROTOBUF_XML_BASE64_H__
#define GOOGLE_PROTOBUF_XML_BASE64_H__

#include <iterator>

namespace google {
namespace protobuf {
namespace xml {

static int _base64Chars[] = {
  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
  'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
  '0','1','2','3','4','5','6','7','8','9',
  '+','/'
};

#define _0000_0011 0x03
#define _1111_1100 0xFC
#define _1111_0000 0xF0
#define _0011_0000 0x30
#define _0011_1100 0x3C
#define _0000_1111 0x0F
#define _1100_0000 0xC0
#define _0011_1111 0x3F

#define _EQUAL_CHAR   (-1)
#define _UNKNOWN_CHAR (-2)

#define _IOS_FAILBIT   std::ios_base::failbit
#define _IOS_EOFBIT    std::ios_base::eofbit
#define _IOS_BADBIT    std::ios_base::badbit
#define _IOS_GOODBIT   std::ios_base::goodbit

//!@private
// TEMPLATE CLASS base64_put
template<class _E = char, class _Tr = std::char_traits<_E> >
class Base64
{
public:

    typedef unsigned char byte_t;
    typedef _E            char_type;
    typedef _Tr           traits_type; 

    // base64 requires max line length <= 72 characters
    // you can fill end of line
    // it may be crlf, crlfsp, noline or other class like it


    //!@private
    struct crlf
    {
        template<class _OI>
            _OI operator()(_OI _To) const{
            *_To = _Tr::to_char_type('\r'); ++_To;
            *_To = _Tr::to_char_type('\n'); ++_To;

            return (_To);
        }
    };


    //!@private
    struct crlfsp
    {
        template<class _OI>
            _OI operator()(_OI _To) const{
            *_To = _Tr::to_char_type('\r'); ++_To;
            *_To = _Tr::to_char_type('\n'); ++_To;
            *_To = _Tr::to_char_type(' '); ++_To;

            return (_To);
        }
    };

    //!@private
    struct noline
    {
        template<class _OI>
            _OI operator()(_OI _To) const{
            return (_To);
        }
    };

    //!@private
    struct three2four
    {
        void zero()
        {
            _data[0] = 0;
            _data[1] = 0;
            _data[2] = 0;
        }

        byte_t get_0()    const
        {
            return _data[0];
        }
        byte_t get_1()    const
        {
            return _data[1];
        }
        byte_t get_2()    const
        {
            return _data[2];
        }

        void set_0(byte_t _ch)
        {
            _data[0] = _ch;
        }

        void set_1(byte_t _ch)
        {
            _data[1] = _ch;
        }

        void set_2(byte_t _ch)
        {
            _data[2] = _ch;
        }

        // 0000 0000  1111 1111  2222 2222
        // xxxx xxxx  xxxx xxxx  xxxx xxxx
        // 0000 0011  1111 2222  2233 3333

        int b64_0()    const    {return (_data[0] & _1111_1100) >> 2;}
        int b64_1()    const    {return ((_data[0] & _0000_0011) << 4) + ((_data[1] & _1111_0000)>>4);}
        int b64_2()    const    {return ((_data[1] & _0000_1111) << 2) + ((_data[2] & _1100_0000)>>6);}
        int b64_3()    const    {return (_data[2] & _0011_1111);}

        void b64_0(int _ch)    {_data[0] = ((_ch & _0011_1111) << 2) | (_0000_0011 & _data[0]);}

        void b64_1(int _ch)    {
            _data[0] = ((_ch & _0011_0000) >> 4) | (_1111_1100 & _data[0]);
            _data[1] = ((_ch & _0000_1111) << 4) | (_0000_1111 & _data[1]);    }

        void b64_2(int _ch)    {
            _data[1] = ((_ch & _0011_1100) >> 2) | (_1111_0000 & _data[1]);
            _data[2] = ((_ch & _0000_0011) << 6) | (_0011_1111 & _data[2]);    }

        void b64_3(int _ch){
            _data[2] = (_ch & _0011_1111) | (_1100_0000 & _data[2]);}

    private:
        byte_t _data[3];

    };




    template<class _II, class _OI, class _State, class _Endline>
        _II put(_II _First, _II _Last, _OI _To, _State& _St, _Endline _Endl)  const
    {
        three2four _3to4;
        int line_octets = 0;

        while(_First != _Last)
        {
            _3to4.zero();

            // 
            _3to4.set_0(*_First);
            _First++;

            if(_First == _Last)
            {
                *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_0()]); ++_To;
                *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_1()]); ++_To;
                *_To = _Tr::to_char_type('='); ++_To;
                *_To = _Tr::to_char_type('='); ++_To;
                goto __end;
            }

            _3to4.set_1(*_First);
            _First++;

            if(_First == _Last)
            {
                *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_0()]); ++_To;
                *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_1()]); ++_To;
                *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_2()]); ++_To;
                *_To = _Tr::to_char_type('='); ++_To;
                goto __end;
            }

            _3to4.set_2(*_First);
            _First++;

            *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_0()]); ++_To;
            *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_1()]); ++_To;
            *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_2()]); ++_To;
            *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_3()]); ++_To;

            if(line_octets == 17) // base64
            {
                //_To = _Endl(_To);
        *_To = '\n'; ++_To;
                line_octets = 0;
            }
            else
                ++line_octets;
        }

        __end: ;

        return (_First);

    }


    template<class _II, class _OI, class _State>
        _II get(_II _First, _II _Last, _OI _To, _State& _St) const
    {
        three2four _3to4;
        int _Char;

        while(_First != _Last)
        {

            // Take octet
            _3to4.zero();

            // -- 0 --
            // Search next valid char... 
            while((_Char =  _getCharType(*_First)) < 0 && _Char == _UNKNOWN_CHAR)
            {
                if(++_First == _Last)
                {
                    _St |= _IOS_FAILBIT|_IOS_EOFBIT; return _First; // unexpected EOF
                }
            }

            if(_Char == _EQUAL_CHAR){
                // Error! First character in octet can't be '='
                _St |= _IOS_FAILBIT; 
                return _First; 
            }
            else
                _3to4.b64_0(_Char);


            // -- 1 --
            // Search next valid char... 
            while(++_First != _Last)
                if((_Char = _getCharType(*_First)) != _UNKNOWN_CHAR)
                    break;

            if(_First == _Last)    {
                _St |= _IOS_FAILBIT|_IOS_EOFBIT; // unexpected EOF 
                return _First;
            }

            if(_Char == _EQUAL_CHAR){
                // Error! Second character in octet can't be '='
                _St |= _IOS_FAILBIT; 
                return _First; 
            }
            else
                _3to4.b64_1(_Char);


            // -- 2 --
            // Search next valid char... 
            while(++_First != _Last)
                if((_Char = _getCharType(*_First)) != _UNKNOWN_CHAR)
                    break;

            if(_First == _Last)    {
                // Error! Unexpected EOF. Must be '=' or base64 character
                _St |= _IOS_FAILBIT|_IOS_EOFBIT; 
                return _First; 
            }

            if(_Char == _EQUAL_CHAR){
                // OK!
                _3to4.b64_2(0); 
                _3to4.b64_3(0); 

                // chek for EOF
                if(++_First == _Last)
                {
                    // Error! Unexpected EOF. Must be '='. Ignore it.
                    //_St |= _IOS_BADBIT|_IOS_EOFBIT;
                    _St |= _IOS_EOFBIT;
                }
                else 
                    if(_getCharType(*_First) != _EQUAL_CHAR)
                    {
                        // Error! Must be '='. Ignore it.
                        //_St |= _IOS_BADBIT;
                    }
                else
                    ++_First; // Skip '='

                // write 1 byte to output
                *_To = (byte_t) _3to4.get_0();
                return _First;
            }
            else
                _3to4.b64_2(_Char);


            // -- 3 --
            // Search next valid char... 
            while(++_First != _Last)
                if((_Char = _getCharType(*_First)) != _UNKNOWN_CHAR)
                    break;

            if(_First == _Last)    {
                // Unexpected EOF. It's error. But ignore it.
                //_St |= _IOS_FAILBIT|_IOS_EOFBIT; 
                    _St |= _IOS_EOFBIT; 
                
                return _First; 
            }

            if(_Char == _EQUAL_CHAR)
            {
                // OK!
                _3to4.b64_3(0); 

                // write to output 2 bytes
                *_To = (byte_t) _3to4.get_0();
                *_To = (byte_t) _3to4.get_1();

                ++_First; // set position to next character

                return _First;
            }
            else
                _3to4.b64_3(_Char);


            // write to output 3 bytes
            *_To = (byte_t) _3to4.get_0();
            *_To = (byte_t) _3to4.get_1();
            *_To = (byte_t) _3to4.get_2();

            ++_First;
            

        } // while(_First != _Last)

        return (_First);
    }

protected:
    
    int _getCharType(int _Ch) const
    {
        if(_base64Chars[62] == _Ch)
            return 62;

        if(_base64Chars[63] == _Ch)
            return 63;

        if((_base64Chars[0] <= _Ch) && (_base64Chars[25] >= _Ch))
            return _Ch - _base64Chars[0];

        if((_base64Chars[26] <= _Ch) && (_base64Chars[51] >= _Ch))
            return _Ch - _base64Chars[26] + 26;

        if((_base64Chars[52] <= _Ch) && (_base64Chars[61] >= _Ch))
            return _Ch - _base64Chars[52] + 52;

        if(_Ch == _Tr::to_int_type('='))
            return _EQUAL_CHAR;

        return _UNKNOWN_CHAR;
    }
};

}  // namespace xml
}  // namespace protobuf
}  // namespace google

#endif    // GOOGLE_PROTOBUF_XML_BASE64_H__
